package org.statewalker.template;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * @author kotelnikov
 * @param <T>
 * @param <D>
 */
public class XmlTemplateRunner<T, D> extends AbstractTemplateRunner<T, D> {

    public interface IDataAccessor<D> {

        void loadData(List<D> input, String link, List<D> output);

    }

    public interface IXmlAccessor<T> {

        Map<String, String> getAttributes(T node);

        String getAttributeValue(T node, String name);

        T getFirstChild(T node);

        T getNextSibling(T child);

        String getNodeName(T node);

        boolean isElement(T node);

        String toString(T node);

    }

    public static abstract class PrintListener<T, D>
        implements
        ITemplateListener<T, D> {

        private IDataAccessor<D> fDataAccessor;

        private IXmlAccessor<T> fXmlAccessor;

        public PrintListener(
            IXmlAccessor<T> xmlAccessor,
            IDataAccessor<D> dataAccessor) {
            fXmlAccessor = xmlAccessor;
            fDataAccessor = dataAccessor;
        }

        public boolean beginNode(T template, D data) {
            boolean result = true;
            if (fXmlAccessor.isElement(template)) {
                printBeginNode(template, data);
                String val = fXmlAccessor.getAttributeValue(template, ST_VALUE);
                if (val != null && !"".equals(val)) {
                    List<D> list = new ArrayList<D>();
                    XmlTemplateRunner.selectValues(
                        fDataAccessor,
                        data,
                        val,
                        list);
                    for (D node : list) {
                        String str = serializeDataToString(node);
                        if (str != null)
                            print(str);
                    }
                    result = false;
                }
            } else {
                String str = fXmlAccessor.toString(template);
                print(str);
            }
            return result;
        }

        public void endNode(T template, D data) {
            if (fXmlAccessor.isElement(template)) {
                printEndNode(template);
            }
        }

        protected Map<String, String> getAttributes(T template, D data) {
            Map<String, String> attributes = fXmlAccessor
                .getAttributes(template);
            if (attributes == null)
                return null;
            String[] array = attributes.keySet().toArray(
                new String[attributes.size()]);
            for (String attrName : array) {
                if (!attrName.startsWith(_ST_PREFIX))
                    continue;
                if (ST_ATTRIBUTES.equals(attrName)) {
                    String attrValue = attributes.get(attrName);
                    String[] values = attrValue.split(";");
                    List<D> output = new ArrayList<D>();
                    for (String pair : values) {
                        output.clear();
                        int idx = pair.indexOf("=");
                        if (idx < 0)
                            continue;
                        String key = pair.substring(0, idx).trim();
                        String path = pair.substring(idx + 1).trim();
                        XmlTemplateRunner.selectValues(
                            fDataAccessor,
                            data,
                            path,
                            output);
                        StringBuilder builder = new StringBuilder();
                        for (D outputData : output) {
                            String str = serializeDataToString(outputData);
                            builder.append(str);
                        }
                        attributes.put(key, builder.toString());
                    }
                }
                attributes.remove(attrName);
            }
            return attributes;
        }

        protected abstract void print(String data);

        protected void printBeginNode(T template, D data) {
            String name = fXmlAccessor.getNodeName(template);
            Map<String, String> attributes = getAttributes(template, data);
            StringBuilder builder = new StringBuilder();
            builder.append("<");
            builder.append(name);
            if (attributes != null) {
                String[] attrNames = attributes.keySet().toArray(
                    new String[attributes.size()]);
                Arrays.sort(attrNames);
                for (String attrName : attrNames) {
                    String attrValue = attributes.get(attrName);
                    builder.append(" ");
                    XmlWriteUtil.escapeName(builder, attrName);
                    builder.append("='");
                    XmlWriteUtil.escapeValue(builder, attrValue);
                    builder.append("'");
                }
            }
            builder.append(">");
            print(builder.toString());
        }

        protected void printEndNode(T template) {
            String name = fXmlAccessor.getNodeName(template);
            print("</" + name + ">");
        }

        protected String serializeDataToString(D node) {
            if (node == null)
                return null;
            return node.toString();
        }

    }

    public static class XmlDataAccessor<D> implements IDataAccessor<D> {

        private IXmlAccessor<D> fXmlAccessor;

        public XmlDataAccessor(IXmlAccessor<D> xmlAccessor) {
            fXmlAccessor = xmlAccessor;
        }

        public void loadData(List<D> input, String link, List<D> output) {
            for (D node : input) {
                if (!fXmlAccessor.isElement(node))
                    continue;
                D child = fXmlAccessor.getFirstChild(node);
                while (child != null) {
                    String name = fXmlAccessor.getNodeName(child);
                    if (link.equals(name))
                        output.add(child);
                    child = fXmlAccessor.getNextSibling(child);
                }
            }
        }
    }

    public static final String _ST_PREFIX = "st-";

    public static final String ST_ATTRIBUTES = _ST_PREFIX + "atts";

    public static final String ST_SELECT = _ST_PREFIX + "select";

    public static final String ST_VALUE = _ST_PREFIX + "value";

    public static final String THIS = "$this";

    @SuppressWarnings("unchecked")
    public static <D> void selectValues(
        IDataAccessor<D> dataAccessor,
        D input,
        String selector,
        List<D> result) {
        selectValues(dataAccessor, Arrays.asList(input), selector, result);
    }

    public static <D> void selectValues(
        IDataAccessor<D> dataAccessor,
        List<D> input,
        String selector,
        List<D> result) {
        if (selector == null)
            selector = "";
        if ("".equals(selector)) {
            result.addAll(input);
            return;
        }
        selector = selector.trim();
        String link;
        int idx = selector.indexOf('/');
        if (idx > 0) {
            link = selector.substring(0, idx);
            selector = selector.substring(idx + 1);
        } else {
            link = selector;
            selector = "";
        }
        List<D> linkedData = input;
        if (link != null
            && !"".equals(link)
            && !".".equals(link)
            && !THIS.equals(link)) {
            linkedData = new ArrayList<D>();
            dataAccessor.loadData(input, link, linkedData);
        }
        selectValues(dataAccessor, linkedData, selector, result);
    }

    private IDataAccessor<D> fDataAccessor;

    protected IXmlAccessor<T> fXmlAccessor;

    public XmlTemplateRunner(
        IXmlAccessor<T> xmlAccessor,
        IDataAccessor<D> dataAccessor) {
        fXmlAccessor = xmlAccessor;
        fDataAccessor = dataAccessor;
    }

    @Override
    protected Iterator<T> getChildren(T node) {
        if (!fXmlAccessor.isElement(node))
            return null;
        List<T> list = new ArrayList<T>();
        T child = fXmlAccessor.getFirstChild(node);
        while (child != null) {
            list.add(child);
            child = fXmlAccessor.getNextSibling(child);
        }
        return list.iterator();
    }

    @SuppressWarnings("unchecked")
    @Override
    protected Iterator<D> selectData(T node, D data) {
        if (!fXmlAccessor.isElement(node))
            return null;
        String selector = fXmlAccessor.getAttributeValue(node, ST_SELECT);
        if (selector != null) {
            List<D> result = new ArrayList<D>();
            selectValues(fDataAccessor, data, selector, result);
            return result.iterator();
        }
        return Arrays.asList(data).iterator();
    }

}
